with Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Parts; use Parts;
with Parts.Operations; use Parts.Operations;
with Coordinates; use Coordinates;
with Rotation_Package;
use Rotation_Package;
with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;

package body Solver_Package is

   procedure Put(A : in Array_3ggr29) is
   begin
      for I in 1..29 loop
         for J in 1..3 loop
            Put(A(J,I), Aft => 4, Exp => 0);
            Put(" ");
         end loop;
         New_Line;
      end loop;
   end Put;

   function Check_Duplicate(Coords_List : in Coordinate_List.List_Pointer; Part : in Part_Type;
                             Length : Integer) return Boolean is
   begin
      for A in 1..Length loop
         if Member(Coords_List,Part.Part_Pos(A).Coords_List) then
            return True;
         end if;
      end loop;
      return False;
   end Check_Duplicate;

   procedure Find_Possible_Positions(Part : in out Part_List.List_Pointer;
                                     Part_Number : in Integer;
                                     Full_Figure : in Part_List.List_Pointer;
                                     Figure_Number : in Integer) is
      Coords_List, Prev_Rot_Coords_List, Temp_Move_Coords_List : Coordinate_List.List_Pointer;
      Temp_Part : Part_Type;
      Orig_Part : Part_Type;
      Temp_Figure : Part_Type;
      Rotation_Array : Array_3ggr29;
      Unique_Rotation : Boolean := True;
      Position,P : Integer := 1;
      X_Boundary, Y_Boundary, Z_Boundary :  Integer;
   begin
      --- FULKOD!!!
      Orig_Part := Part_List.Find(Part, Part_Number);
      Temp_Part := Part_List.Find(Part, Part_Number);
      Temp_Figure := Part_List.Find(Full_Figure, Figure_Number);

      X_Boundary := Temp_Figure.X;
      Y_Boundary := Temp_Figure.Y;
      Z_Boundary := Temp_Figure.Z;
      ---/FULKOD!!!

      -- Skapar en array som innehåller alla unika rotationer för en bit
      for I in 1..3 loop
         for J in 1..29 loop
            Rotation_Array(I,J) := 0.0;
         end loop;
      end loop;

      for I in 1..4 loop
         Rotation_Array(1,I) :=  Ada.Numerics.Pi/2.0;
         Rotation_Array(3,(I+5)) := Ada.Numerics.Pi/2.0;
         Rotation_Array(1,(I+10)) := Ada.Numerics.Pi/2.0;
         Rotation_Array(3,(I+15)) := Ada.Numerics.Pi/2.0;
         Rotation_Array(2,(I+20)) := Ada.Numerics.Pi/2.0;
         Rotation_Array(2,(I+25)) := Ada.Numerics.Pi/2.0;
      end loop;
      Rotation_Array(2,5) := Ada.Numerics.Pi/2.0;
      Rotation_Array(2,10) := Ada.Numerics.Pi/2.0;
      Rotation_Array(2,15) := Ada.Numerics.Pi/2.0;
      Rotation_Array(1,20) := Ada.Numerics.Pi/2.0;
      Rotation_Array(1,25) := Ada.Numerics.Pi;
      --Put(Rotation_Array);
      -----------------------------------------------------------------

      -- Roterar biten till en ny unik rotationsriktning
      Coordinate_List.Copy(Orig_Part.Coordinates,Coords_List);
      Coordinate_List.Copy(Orig_Part.Coordinates,Prev_Rot_Coords_List);
      for R in 1..29 loop
         Coords_List := Prev_Rot_Coords_List;
         case R is
            when 1..4|11..14 =>
               Put("Före rot:");
               New_Line;
               Coordinate_List.Put(Coords_List);
               New_Line;
               Rotate_Part(Coords_List,Rotation_Array(1,R),'X');
               Put("Efter rotation");
               New_Line;
               Coordinate_List.Put(Coords_List);
               New_Line;
               Unique_Rotation := True;
            when 6..9|16..19 =>
               Rotate_Part(Coords_List,Rotation_Array(3,R),'Z');
               Unique_Rotation := True;
            when 21..24|26..29 =>
               Rotate_Part(Coords_List,Rotation_Array(2,R),'Y');
               Unique_Rotation := True;
            when 5|10|15 =>
               Rotate_Part(Coords_List,Rotation_Array(2,R),'Y');
               Unique_Rotation := False; -- Ej en unik rotation
            when 20 =>
               Rotate_Part(Coords_List,Rotation_Array(1,R), 'X');
               Unique_Rotation := False; -- Ej en unik rotation
            when 25 =>
               Rotate_Part(Coords_List,Rotation_Array(1,R), 'X');
               Unique_Rotation := False; -- Ej en unik rotation
            when others =>
               null;
         end case;
         Prev_Rot_Coords_List := Coordinate_List.Copy(Coords_List);

         ---------------------------------------------------------

         -- Utför alla möjliga translationer för den unika rotationsriktningen
         if Unique_Rotation then
            for X in 1..X_Boundary loop
               for Y in 1..Y_Boundary loop
                  for Z in 1..Z_Boundary loop
                     Temp_Move_Coords_List := Move_Part(Coords_List,X-1,Y-1,Z-1);
                     P := P + 1;
                   --  Put("efter förflyttning");
                   --  New_Line;
                   --  Coordinate_List.Put(Temp_Move_Coords_List);

                     -- Kollar om biten ligger innanför figurens yttre granser
                     if Member(Temp_Move_Coords_List,Temp_Figure.Coordinates) then
                        Put("-----------------");
                        New_Line;
                        if Position > 1 then
                           -- Kollar sa att det ar en unik position
                           if not Check_Duplicate(Temp_Move_Coords_List,Orig_Part,Position-1) then

                              -- Lagger till den nya rot/pos av biten i arrayen
                              Orig_Part.Part_Pos(Position).R := R;
                              Orig_Part.Part_Pos(Position).X := X-1;
                              Orig_Part.Part_Pos(Position).Y := Y-1;
                              Orig_Part.Part_Pos(Position).Z := Z-1;
                              Coordinate_List.Copy(Temp_Move_Coords_List, Orig_Part.Part_Pos(Position).Coords_List);
                              Position := Position + 1;
                           end if;
                        else
                           Coordinate_List.Copy(Temp_Move_Coords_List, Orig_Part.Part_Pos(Position).Coords_list);
                           Position := Position + 1;
                        end if;
                     end if;
                  end loop;
               end loop;
            end loop;
         end if;
      end loop;
      --Orig_Part.Part_Pos(Position) := null;
      -- MER FULKOD!
      Orig_Part.Number_Of_Possible_Positions := Position-1;
      Part_List.Remove(Part,Part_Number);
      Part_List.Insert(Part,Orig_Part);
      --/ FULKOD
      Put(P);
   end Find_Possible_Positions;


   procedure Find_All_Possible_Positions(Parts_List : in out Part_List.List_Pointer;
                                         Full_Figure : in out Part_List.List_Pointer;
                                        Figure_Number : in integer) is
      Temp_Part_List : Part_List.List_Pointer := Parts_list;
   begin
      for I in 1..Part_List.Length(Parts_List) loop
         Find_Possible_Positions(Temp_Part_List, I, Full_Figure, 1);
         Part_List.Next(Temp_Part_List);
      end loop;

   end Find_All_Possible_Positions;

   function Check_Intersection(Partial_figure : in Coordinate_List.List_Pointer; Coords : Coordinate_List.List_Pointer)
                              return Boolean is
      Tmp_Figure_Pointer, Tmp_Coords_Pointer : Coordinate_List.List_Pointer;
      Tmp_Coords_Data : Int_Vector;
      Tmp_Figure_Data : Int_vector;
   begin
      if Coordinate_List.Empty(Partial_Figure) or Coordinate_List.Empty(Coords) then
         return False;
      end if;
      Tmp_Figure_Pointer := Partial_Figure;
      Tmp_Coords_Pointer := Coords;
      for I in 1..Coordinate_List.Length(coords) loop
         Tmp_Coords_Data := Coordinate_List.Find(Tmp_Coords_Pointer, I).coordinates;
         for J in 1..Coordinate_List.Length(Partial_Figure) loop
            Tmp_Figure_Data := Coordinate_List.Find(Tmp_Figure_Pointer, J).coordinates;
            if Tmp_Coords_Data = Tmp_Figure_Data then
               return True;
            end if;
         end loop;
      end loop;
      return False;
   end Check_Intersection;

   function Try_To_Build_Figure_With_Parts(Parts_List : in Part_List.List_Pointer;
                                           Full_Figure : in Part_List.List_Pointer;
                                           Partial_Figure : in Part_List.List_Pointer) return Boolean is
      Next_Figure, Next_Part, Fuck_pointer : Part_List.List_Pointer;
      Temp_Part, Temp_Figure, Next_Figure_Data : Part_Type;
   begin
      --if Part_List.Empty(Temp_Figure) then
      --   Temp_Figure := new Part_Type;
      -- end if;
      -- MASSA FULKOD FÖR ATT MODIFIERA DATA I LSITAN :/
      if not Part_List.Empty(Parts_List) then
         Next_Part := Parts_List;

         Part_List.Next(Next_Part);
         Fuck_Pointer := Parts_List;
        -- Put("FÖRSTA FINDEN");
     --    New_Line;
         Temp_Part := Part_List.Get_First_Item(Parts_List);
        -- Put("ANDRA FINDEN");
      --   New_Line;
         if not Part_List.Empty(Partial_Figure) then
            Temp_Figure := Part_List.Get_First_Item(Partial_Figure);
         end if;
         --  Put("LALALAAL");
       --  Put("*");
       --  New_Line;
       --  Part_List.Put(Partial_Figure);
        -- Put("*****");
      --   Put(Temp_Part.Number_Of_Possible_Positions);
         for I in 1..Temp_Part.Number_Of_Possible_Positions loop
           -- Put("I LOOPEN");

         --   Put(I);
               if Check_Intersection(Temp_Figure.Coordinates, Temp_Part.Part_Pos(I).Coords_list) = False then
                 -- Put("TEST");
                --  Put("TMP FIGURE");
                 -- Put(Temp_Figure);
               --   New_Line;
                  Next_Figure_Data.coordinates := Expand_Coordinate_List(Temp_Figure.Coordinates,
                                                                         Temp_Part.Part_Pos(I).Coords_list);
                  Next_Figure_Data.Part_Nr := 1;
               --   New_Line;
                --  Put("<(*;*)>");
               --   New_Line;
                  Part_List.Insert(Next_Figure, Next_Figure_Data);
                  Temp_Part.Possible_Solution_Index := I;
               --   New_Line;
                --  Put("Key: ");
               --   Put(Temp_Part.Part_Nr);
               --   New_Line;
                  Part_List.Remove(Fuck_pointer, Temp_Part.Part_Nr);
                  Part_List.Insert(Fuck_pointer, Temp_Part);
                  if (Try_To_Build_Figure_With_Parts(Next_Part, Full_Figure, Next_Figure)) = True then
                     return True;
                  else
               --      New_Line;
               --      Put("Deletetetett");
               --      New_Line;
                     Coordinate_list.Delete(Next_Figure_Data.coordinates);
                     Part_List.Delete(Next_Figure);
               --      Put("DELETAT");
                  end if;
               end if;
            end loop;
        return False;
      end if;
      return True;
   end Try_To_Build_Figure_With_Parts;


   procedure R_To_Rotation_Angle(R : in Integer;
                          Theta_X : out Integer;
                          Theta_Y : out Integer;
                          Theta_Z : out Integer) is -- Obs tänk på att R = 5,10,15,20,25 ej kommer att vara ett input till denna funktion
   begin
      Theta_X := 0;
      Theta_Y := 0;
      Theta_Z := 0;
      for I in 1..R loop
         case R is
            when 1..4|11..14 =>
               Theta_X := Theta_X + 1;
            when 6..9|16..19 =>
               Theta_Z := Theta_Z + 1;
            when 21..24|26..29 =>
               Theta_Y := Theta_Y + 1;
            when 5|10|15 =>
               Theta_Y := Theta_Y + 1; -- Ej en unik rotation
            when 20 =>
               Theta_X := Theta_X + 1; -- Ej en unik rotation
            when 25 =>
               Theta_X := Theta_X + 2; -- Ej en unik rotation
            when others =>
               null;
         end case;

         if Theta_X = 4 then
            Theta_X := 0;
         end if;
         if Theta_Y = 4 then
            Theta_Y := 0;
         end if;
         if Theta_Z = 4 then
            Theta_Z := 0;
         end if;
      end loop;
   end R_To_Rotation_Angle;


   function Solution_Message(List : in Part_List.List_Pointer)
                            return Unbounded_String is
      Temp_List : Part_List.List_Pointer := list;
      Temp_Part : Part_Type;
      X_Rot, Y_Rot, Z_Rot, X_Trans, Y_Trans, Z_Trans, Size : Integer;
      Output : Unbounded_String;
   begin
      Size := Part_List.Length(Temp_List);
      Output := To_Unbounded_String(Integer'Image(Size));
      while not Part_List.Empty(Temp_List) loop
         Temp_Part := Part_List.Get_First_Item(Temp_List);
         R_To_Rotation_Angle(Temp_Part.Part_Pos(Temp_Part.Possible_Solution_index).R, X_Rot, Y_Rot, Z_Rot);
         X_Trans := Temp_Part.Part_Pos(Temp_Part.Possible_Solution_index).X;
         Y_Trans := Temp_Part.Part_Pos(Temp_Part.Possible_Solution_index).Y;
         Z_Trans := Temp_Part.Part_Pos(Temp_Part.Possible_Solution_index).Z;
         Output := Output & " " & "!" & " " & Integer'Image(X_Rot) & " " & Integer'Image(Y_Rot) & " " & Integer'Image(Z_Rot) & " " & Integer'Image(X_Trans) & " " & Integer'Image(Y_Trans) & " " & Integer'Image(Z_Trans);
         Part_List.Next(Temp_List);
      end loop;
      Put(To_String(Output));
      return Output;
   end Solution_Message;

   function Server_Check_Solution(Sol_Message : in String_Array;
                                  List : in Part_List.List_Pointer;
                                  Full_Figure : in Part_List.List_Pointer;
                                  Figure_Number : in Integer) return Boolean is
      Size : Integer;
      Part : Part_Type;
      Temp_Figure, Solution_Figure, Coords_List : Coordinate_List.List_Pointer;
      J : Integer := 0;
   begin
      Size := Integer'Value(To_String(Sol_Message(1)));
      Temp_Figure := Part_List.Find(Full_Figure, Figure_Number).Coordinates;

      for I in 1..Size loop
         Coords_List := Part_List.Find(List,I).Coordinates;
         Rotate_Part(Coords_List,Float'value(To_String(Sol_Message(3+J)))*90.0,'X');
         Rotate_Part(Coords_List,Float'value(To_String(Sol_Message(4+J)))*90.0,'Y');
         Rotate_Part(Coords_List,Float'value(To_String(Sol_Message(5+J)))*90.0,'Z');
         Move_Part(Coords_List,Integer'value(To_String(Sol_Message(6+J))),Integer'value(To_String(Sol_Message(7+J))),Integer'value(To_String(Sol_Message(8+J))));
         Solution_Figure := Expand_Coordinate_List(Solution_Figure, Coords_List);
         J := J + 7;
      end loop;

      if Member(Solution_Figure, Temp_Figure) and Member(Temp_Figure, Solution_Figure) then
         return True;
      else
         return False;
      end if;
   end Server_Check_Solution;

end Solver_Package;

